// 简答题
/**
  答：
  js的异步编程。在node环境下，js的执行线程是单线程，耗时操作会交给子线程处理（I/O，网络，更新UI等等），处理完成后将回调函数放入EventQueue中，等待主线程的调用，
    
  当js开始执行，js代码作为第一个宏任务开始主线程执行，执行过程中产生的异步任务会进入EventTable 并注册回调函数，当前宏任务代码继续向下执行。
    当异步任务执行完毕之后，EventTable会将回调函数移入 EventQueue，其中宏任务（setTimeOut等）移动到宏任务的EventQueue，微任务（process.nextTick等）会移动到微任务的eventQueue。
    当前宏任务执行完毕后，去读取微任务的EventQueue，按照先入先出的原则按序执行微任务回调。执行完毕，一轮宏任务事件循环结束。
    下一轮事件循环开始，主线程从宏任务EventQueue中取出一个宏任务到主线程中开始执行，同上这个宏任务的执行过程中可能会产生
    异步任务，任务回调同样分别放入宏任务EventQueue和微任务EventQueue，本轮宏任务执行完成后立即执行微任务EventQueue中的微任务，本轮宏任务执行完毕。
    再再再开始下一轮的宏任务。。。。。。
    这样的一个不断检查EventQueue，提取任务，执行任务，异步处理，新的任务或回调又放入EventQueue 的过程就是事件循环EventLoop
*/


// 代码题

/**
 *   ================    第一题    =================
 */
function timeOut(str) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(str);
    }, 10);
  });
}

timeOut('hello')
  .then(res => timeOut(res + ' lagou'))
  .then(res => timeOut(res + ' I ❤️ U'))
  .then(res => console.log(res));


/**
 *   ================    第二题    =================
 */
const fp = require('lodash/fp');
const { cars } = require('./dataSource');
/**
 *  练习1
 */
let isLastInStock = fp.flowRight(fp.prop('in_stock'), fp.last);
console.log(isLastInStock(cars));

/**
 *  练习2
 */
let firstName = fp.flowRight(fp.prop('name'), fp.first);
console.log(firstName(cars));

/**
 *  练习3
 */
let _average = function (xs) {
  return fp.reduce(fp.add, 0, xs) / xs.length;
}

let averageDollarValue = fp.flowRight(_average, fp.map(v => v.dollar_value));
console.log((averageDollarValue(cars)));

/**
 *  练习4
 */
let _underscore = fp.replace(/\W+/g, '_');
let sanitizeNames = fp.map(fp.flowRight(_underscore, fp.toLower, v => v.name));
console.log(sanitizeNames(cars));


/**
 *   ================    第三题    =================
 */

/**
 *  练习1
 */
const { Maybe, Container } = require('./support');
const { reject } = require('lodash');
let maybe = Maybe.of([5, 6, 1])

let ex1 = (num) => {
  return maybe.map(fp.map(fp.add(num)))
}
console.log(ex1(2)._value)

/**
 *  练习2
 */
let xs = Container.of(['do', 'ray', 'me', 'fa', 'so', 'la', 'ti', 'do'])
let ex2 = () => {
  return xs.map(fp.first)
}
console.log(ex2(xs)._value)

/**
 *  练习3
 */
let safeProp = fp.curry(function (x, o) {
  return Maybe.of(o[x])
})
let user = { id: 2, name: 'albert' }
let ex3 = () => {
  return safeProp('name', user).map(fp.first)
}
console.log(ex3()._value)

/**
 *  练习4
 */
let ex4 = function (n) {
  return Maybe.of(n).map(parseInt)._value
}
console.log(ex4('123dd'))


/**
 *   ================    第四题    =================
 */

/**
 *  promise用new创建,是一个类或构造函数,此处用类来实现
 * 考虑到敏感属性不能对外暴露,使用了ES2019的私有属性,运行时使用babel-node 命令运行
 */
class MyPromise {

  /**
   *  定义promise的三种状态
   */
  static #pendening = 'pendening';
  static #fullfilled = 'fullfilled';
  static #rejected = 'rejected';

  // 状态属性,默认状态是等待状态 私有的
  #type = 'pendening'
  // 存储值
  #value = undefined
  // 存储错误
  #err = undefined

  // then处理函数数组,当Promise处于pending状态时,then方法的处理函数暂存到这里
  #promiseHandleArray = []

  // 初始化promise时会接收一个函数,函数接收两个参数,一个成的回调,一个失败的回调
  constructor(callFunc) {
    // promise初始化函数是同步执行,成功和失败的回调是promise的方法
    callFunc(this.#resolve, this.#reject);
  }

  // 成功的回调,为了不被外部修改this,此处使用箭头函数
  #resolve = (data) => {
    // 如果状态确定,不做任何操作
    if (this.#type !== MyPromise.#pendening) return;
    // 修改状态为成功
    this.#type = MyPromise.#fullfilled;
    // 保存值
    this.#value = data;
    // 处理then的回调
    this.#executeCallback()
  }

  // 失败的回调,为了不被外部修改this,此处使用箭头函数
  #reject = (err) => {
    // 如果状态确定,不做任何操作
    if (this.#type !== MyPromise.#pendening) return;
    // 修改状态为失败
    this.#type = MyPromise.#rejected;
    // 保存错误
    this.#err = err;
    // 处理then的回调
    this.#executeCallback()
  }

  // 分别按顺序执行回调,并清空回调数组
  #executeCallback = () => {
    this.#promiseHandleArray.forEach(handle => handle())
    this.#promiseHandleArray = []
  }

  /**
   *  then方法用于接收Promise处理结果并再加工,返回一个包含再加工的Promise
   *  这样就把处理一个步骤一个步骤的延续下去
   */
  then = (succeCallback, failedCallback) => {

    // then需要返回一个promise 
    return new MyPromise((resolve, reject) => {
      // 处于都外部调用的不信任,判断外部传单,如果不是函数型,就默认为 v=>v ,这样可以把数据传递给下一个promise
      succeCallback = (succeCallback instanceof Function) ? succeCallback : v => v
      // 同上 ,catch正是利用了这一点,在连续的then处理中,只要不传failedCallback,错误会一层层传递下去,直到被catch捕获.
      failedCallback = (failedCallback instanceof Function) ? failedCallback : e => { throw e }

      // 当promise状态确定之后,统一调用这个处理函数,处理
      let promiseHandle = () => {
        // 启动一个微任务
        queueMicrotask(() => {
          try {
            let result
            if (this.#type == MyPromise.#fullfilled) {
              // 当前状态成功时,调用成功函数并传入当前值,得到对应的返回值.
              result = succeCallback(this.#value)
            } else if (this.#type == MyPromise.#rejected) {
              // 当前状态失败,调用失败函数传入当前值,得到对应的返回值.
              result = failedCallback(this.#err)
            }

            // 如果返回值是promise ,那么返回的promise状态由返回值状态决定,
            // 如果返回值是不是promise,那么将返回promise置为成功状态
            if (result instanceof MyPromise) {
              result.then(resolve, reject)
            } else {
              resolve(result)
            }

          } catch (error) {
            // 过程中发生了错误,可能是succeCallback中throw了错误,可能是failedCallback中throw了错误.
            // 此时将返回promise置为失败,并传递error对象
            reject(error)
          }
        })
      }

      // 如果当前状态是等待状态,就将处理函数放入处理函数数组,等待状态确定时回调
      // 如果状态确定,那么直接回调
      if (this.#type === MyPromise.#pendening) {
        this.#promiseHandleArray.push(promiseHandle)
      } else {
        promiseHandle()
      }
    })
  }

  // 便利的创建一个成功状态的promise
  static resolve(value) {
    return new MyPromise((resolve, reject) => resolve(value))
  }

  // 便利的创建一个失败状态的promise
  static reject(err) {
    return new MyPromise((resolve, reject) => reject(err))
  }

  // 传入一个数组,等待其中的promise都成功,返回的promise就成功,否则返回的promise是失败状态
  static all(pArray) {
    return new MyPromise((resolve, reject) => {
      let result = []
      let dataCount = 0
      // 设置值,每次设置count++ ,知道所有的值都成功到位,将promise置为成功
      let addData = (index, value) => {
        result[index] = value;
        dataCount++;
        if (dataCount === pArray.length) {
          resolve(result);
        }
      }
      // 遍历数组每个元素,如果元素是promise就等待拿到他的值,如果元素是值,直接设置值.
      pArray.forEach((p, i) => {
        if (p instanceof MyPromise) {
          p.then(value => {
            addData(i, value)
          }).catch(err => {
            reject(err)
          })
        } else {
          addData(i, p);
        }
      })
    })
  }

  // 便利的创建一个成功状态的promise
  catch(errCallback) {
    return this.then(null, errCallback)
  }
}


// 测试代码
let p1 = new MyPromise((resolve, reject) => {
  resolve('99999')
});

p1.then(() => MyPromise.reject('中途失败'))
  .then(() => { throw '再次失败' })
  .catch(err => {
    console.log('err1', err)
  })










